﻿using Client.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;

[Serializable]
public class KeyObjectData
{
    public string name;
    public Component com;
}

[DisallowMultipleComponent]
public class SerializeNode : MonoBehaviour, ISerializationCallbackReceiver
{
    [HideInInspector]
    public int maskIdx = 1;
    [HideInInspector]
    public string[] names = new string[0];

    readonly string strMatch = @"[\(（][\s\S]*[\)）]";
    [HideInInspector]
    public List<KeyObjectData> keyObjDatas = new List<KeyObjectData>();
    Dictionary<string, Component> keyValues = new Dictionary<string, Component>();

    /// <summary>
    /// 序列化前触发方法
    /// </summary>
    public void OnBeforeSerialize()
    {
        keyObjDatas.ForEach(item =>
        {
            if (item.com && !IsValidName(item.com.name))
            {
                Debug.LogWarning($"{item.com.name} is not invalid keyName", item.com);
                item.com.name = GetObjName(item.com.name);
            }
        });
    }

    public void OnAfterDeserialize()
    {
        keyValues.Clear();
        AddKeyValue(keyObjDatas);
    }

    protected void AddKeyValue(List<KeyObjectData> keyObjects)
    {
        keyObjects.ForEach(item =>
        {
            if (!keyValues.ContainsKey(item.name))
            {
                keyValues[item.name] = item.com;
            }

            if (IsSerializeNode(item.com))
            {
                var node = (SerializeNode)item.com;
                AddKeyValue(node.keyObjDatas);
            }
        });
    }

    public T GetObject<T>(string key) where T : class
    {
        var com = GetObject(key);
        if (com != null)
        {
            return com as T;
        }
        return null;
    }

    public Component GetObject(string key)
    {
        keyValues.TryGetValue(key, out Component com);
        return com;
    }

    public bool IsValidName(string objName)
    {
        return UITools.IsValidName(objName);
    }

    public string GetObjName(string objName)
    {
        objName = Regex.Replace(objName, strMatch, "");
        return objName.Replace(" ", "");
    }

    public string GetHashCodeName(Component com)
    {
        string objName = GetObjName(com.name);
        string typeName = UITools.GetTypeString(com);
        return $"{objName}_{typeName}";
    }

    protected bool Contains(string name)
    {
        foreach (var item in keyObjDatas)
        {
            if (item.name == name)
            {
                return true;
            }
        }
        return false;
    }


    protected void Delete(Component com)
    {
        for (int i = keyObjDatas.Count - 1; i >= 0; i--)
        {
            if (keyObjDatas[i].com == com)
            {
                keyObjDatas.RemoveAt(i);
            }
        }
    }

    protected void Add(Component com)
    {
        string name = GetHashCodeName(com);
        if (!Contains(name))
        {
            keyObjDatas.Add(new KeyObjectData()
            {
                com = com,
                name = name,
            });
        }
        else { Debug.Log("重复添加", com); }
    }

    public virtual void FlushData()
    {
        keyObjDatas.Clear();  //清空列表数据
        names = AddKeyObjData(gameObject, maskIdx);
    }

    protected string[] AddKeyObjData(GameObject go, int maskIdx)
    {
        var comTypes = GetComTypeEnum(go, out string[] comNames);
        Component[] coms = Enumerable.ToArray(comTypes.Values);

        for (int i = 0; i < coms.Length; i++)
        {
            int result = 1 << i;
            int value = result & maskIdx;

            if (value == result)
            {
                Add(coms[i]);
            }
            else { Delete(coms[i]); }
        }
        return comNames;
    }

    public Dictionary<string, Component> GetComTypeEnum(GameObject go, out string[] comNames)
    {
        var comTypes = new Dictionary<string, Component>();
        Component[] coms = go.GetComponents<Component>();

        foreach (var com in coms)
        {
            if (com == null)
            {
                Debug.LogWarning("found object component missing", go);
                continue;
            }
            if (com is SerializeNode) { continue; }

            string key = com.GetTypeName();
            if (!comTypes.ContainsKey(key))
            {
                comTypes[key] = com;
            }
        }
        comNames = Enumerable.ToArray(comTypes.Keys);
        return comTypes;
    }

    public string GetFieldString()
    {
        string tempStr = "public {0} {1} ";
        var stringBuilder = new StringBuilder();

        keyObjDatas.ForEach(item =>
        {
            if (IsSerializeNode(item.com))
            {
                var node = (SerializeNode)item.com;
                stringBuilder.Append(node.GetFieldString());
            }
            else
            {
                stringBuilder.Append("\n\t");
                Type type = item.com.GetType();

                string fullName = UITools.GetTypeFullName(type);
                stringBuilder.AppendFormat(tempStr, fullName, item.name);
                stringBuilder.Append("{ get; private set; }");
            }
        });
        return stringBuilder.ToString();
    }

    public string GetBodyString()
    {
        string tempStr = "{0} = Root.GetObject<{1}>(\"{2}\");";
        var stringBuilder = new StringBuilder();

        keyObjDatas.ForEach(item =>
        {
            if (IsSerializeNode(item.com))
            {
                var node = (SerializeNode)item.com;
                stringBuilder.Append(node.GetBodyString());
            }
            else
            {
                stringBuilder.Append("\n\t\t");
                Type type = item.com.GetType();

                string fullName = UITools.GetTypeFullName(type);
                stringBuilder.AppendFormat(tempStr, item.name, fullName, item.name);
            }
        });
        return stringBuilder.ToString();
    }

    public string GetLuaFieldString()
    {
        string tempStr = "\n---@field public {0} CS.{1}";
        var stringBuilder = new StringBuilder();

        keyObjDatas.ForEach(item =>
        {
            if (IsSerializeNode(item.com))
            {
                var node = (SerializeNode)item.com;
                stringBuilder.Append(node.GetLuaFieldString());
            }
            else
            {
                Type type = item.com.GetType();
                stringBuilder.AppendFormat(tempStr, item.name, type.FullName);
            }
        });
        return stringBuilder.ToString();
    }

    public string GetLuaBodyString()
    {
        string tempStr = "self.{0} = self.Root:GetObject('{1}')";
        var stringBuilder = new StringBuilder();

        keyObjDatas.ForEach(item =>
        {
            if (IsSerializeNode(item.com))
            {
                var node = (SerializeNode)item.com;
                stringBuilder.Append(node.GetLuaBodyString());
            }
            else
            {
                stringBuilder.Append("\n\t");
                stringBuilder.AppendFormat(tempStr, item.name, item.name);
            }
        });
        return stringBuilder.ToString();
    }

    public bool IsSerializeNode(Component com)
    {
        if (com != null)
        {
            Type type = typeof(SerializeNode);
            return type == com.GetType();
        }
        return false;
    }
}